<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>4.用参数方程描述曲线</title>
    <style>
        canvas{
            border:1px solid #aaa;
            margin: 0 auto;
            display: block;
        }
    </style>
</head>
<body>
    <canvas width="800" height="800"></canvas>

    <script>
        let canvas = document.querySelector("canvas");
        let ctx = canvas.getContext("2d")

        // 1.设置坐标，坐标变换
        ctx.translate(canvas.width/2, canvas.height/2);//将坐标原点平移到画布最底部
        ctx.scale(1,-1);//翻转y轴，让y轴朝上
        ctx.lineCap = 'round';// 定义线条末端线帽的样式
        
        /**
         * 2.设置向量
         * （1）设置x轴基向量(1,0)
         * （2）设置实际向量：
         *      a.设置x轴基向量一个旋转弧度r，这时基向量的坐标为（x*cosr-y*sinr,y*cosr+x*sinr）,动静坐标转换得到的公式
         *      b.将旋转后的向量，放大L倍，坐标为L（x*cosr-y*sinr,y*cosr+x*sinr）
         */
        class Vector2D extends Array {
            // 生成坐标向量
            constructor(x = 1, y = 0) {
                super(x, y);
            }
            set x(v) {
                this[0] = v;
            }
            set y(v) {
                this[1] = v;
            }
            get x() {
                return this[0];
            }
            get y() {
                return this[1];
            }
            get length() {
                // 计算向量的模
                return Math.hypot(this.x, this.y);
            }
            get dir() {
                // 计算向量的旋转的弧度
                return Math.atan2(this.y, this.x);
            }
            copy() {
                // 复制坐标向量
                return new Vector2D(this.x, this.y);
            }
            // 坐标向量相加
            add(v) {
                this.x += v.x;
                this.y += v.y;
                return this;
            }
            // 坐标向量相减
            sub(v) {
                this.x -= v.x;
                this.y -= v.y;
                return this;
            }
            // 倍数延长坐标向量
            scale(a) {
                this.x *= a;
                this.y *= a;
                return this;
            }
            // 向量叉乘的模
            cross(v) {
                return this.x * v.y - v.x * this.y;
            }
            // 向量点乘的值
            dot(v) {
                return this.x * v.x + v.y * this.y;
            }
            // 向量归一化（向量除以向量的模）
            normalize() {
                return this.scale(1 / this.length);
            }
            // 向量旋转
            rotate(rad) {
                const c = Math.cos(rad),
                s = Math.sin(rad);
                const [x, y] = this;

                this.x = x * c + y * -s;
                this.y = x * s + y * c;

                return this;
            }
        }
        
        // 根据坐标绘制线段
        function draw(points, strokeStyle = 'black', fillStyle = null) {
            ctx.strokeStyle = strokeStyle;
            ctx.beginPath();
            ctx.moveTo(...points[0]);
            for(let i = 1; i < points.length; i++) {
                ctx.lineTo(...points[i]);
            }
            ctx.closePath();
            if(fillStyle) {
                ctx.fillStyle = fillStyle;
                ctx.fill();
            }
            ctx.stroke();
        }

        /**
         * 3.画圆
         * @param { number } x0 起点
         * @param { number } y0 起点
         * @param { number } radius 半径长度
         * @param { number } startAng 起始角度
         * @param { number } endAng 结束角度
         * 圆的计算公式，y=y0+r*sinα，x=x0+r*cosα
         * 注意：canvas中也有ctx.arc（）方法，但这里是为了兼容其他不支持的系统
         */ 
        
        const TAU_SEGMENTS = 60;
        const TAU = Math.PI * 2;
        
        function arc(x0, y0, radius, startAng = 0, endAng = Math.PI * 2) {
            // 获取圆的最大弧度
            const ang = Math.min(TAU, endAng - startAng);
            // 如果弧度是2π，那就定义一个空数组，否则加入圆点（为了生成扇形区域）
            const ret = ang === TAU ? [] : [[x0, y0]];
            // 将圆弧按照比例分成片段的数量
            const segments = Math.round(TAU_SEGMENTS * ang / TAU);
            for(let i = 0; i <= segments; i++) {
                // 将小片段的坐标点加入集合中，ang / segments表示每个片段的弧度大小
                // 通过圆的计算公式计算坐标点，y=y0+r*sinα，x=x0+r*cosα
                const x = x0 + radius * Math.cos(startAng + ang * i / segments);
                const y = y0 + radius * Math.sin(startAng + ang * i / segments);
                ret.push([x, y]);
            }
            return ret;
        }

        draw(arc(-200, 200, 100)); // 绘制圆

        /**
         * 4.画椭圆
         * @param { number } x0 起点
         * @param { number } y0 起点
         * @param { number } a 长轴
         * @param { number } b 短轴
         * @param { number } startAng 起始角度
         * @param { number } endAng 结束角度
         */ 
         function ellipse(x0, y0, a, b, startAng = 0, endAng = Math.PI * 2) {
            // 获取圆的最大弧度
            const ang = Math.min(TAU, endAng - startAng);
            // 如果弧度是2π，那就定义一个空数组，否则加入圆点（为了生成扇形区域）
            const ret = ang === TAU ? [] : [[x0, y0]];
            // 将圆弧按照比例分成片段的数量
            const segments = Math.round(TAU_SEGMENTS * ang / TAU);
            for(let i = 0; i <= segments; i++) {
                // 将小片段的坐标点加入集合中，ang / segments表示每个片段的弧度大小
                const x = x0 + a * Math.cos(startAng + ang * i / segments);
                const y = y0 + b * Math.sin(startAng + ang * i / segments);
                ret.push([x, y]);
            }
            return ret;
        }

        draw(ellipse(200, 200, 100, 80)); // 画椭圆


        /**
         * 5.抛物线
         * @param { number } x0 起点
         * @param { number } y0 起点
         * @param { number } p 焦点到准线的距离
         * @param { number } min 最小值
         * @param { number } max 最大值
         * 
         * 抛物线的计算公式，y = y0 + 2 * pt，x = x0 + 2 * pt^2
         * 
         */ 
         const LINE_SEGMENTS = 60;
        function parabola(x0, y0, p, min, max) {
            const ret = [];
            for(let i = 0; i <= LINE_SEGMENTS; i++) {
                // 将抛物线按照60份平均分
                const s = i / 60; 
                // 将t设置为自变量，根据给定的范围，与区间片段，计算出所有的坐标点
                const t = min * (1 - s) + max * s;
                // 计算所有的坐标点 
                const x = x0 + 2 * p * t ** 2; 
                const y = y0 + 2 * p * t; 
                ret.push([x, y]);
            }
            return ret;
        }

        draw(parabola(-400, -200, 6, -5, 5)); // 画抛物线

    </script>
</body>
</html>